#include <iostream>
#include <sstream>
#include "View3D.h"
#include "config.h"
#include <memory>

using namespace std;

/*
 * Constructeur de la vue 3D qui initialise tous les attributs
 * nécessaires.
 */
View3D::View3D() :
		View() {
	PacMan pacman = model->getPacMan();
	std::shared_ptr < GLUquadricObj
			> smartQuadBiere(gluNewQuadric(), gluDeleteQuadric); // initalisation du phare pour dessiner la voiture
	quadBiere = smartQuadBiere.get(); // création d'un smart pointer. Tant que celui-ci n'est pas détruit, il gérera tous le raws pointers.
	//Utilisation d'un shared pointer afun de pouvoir spécifier un constructeur particulier
	gluQuadricNormals(quadBiere, GLU_SMOOTH); // effet smooth
	gluQuadricTexture(quadBiere, GL_TRUE); // coordonnées textures

	/*Pour la avoir un effet de shininess*/
	mat_specular[0] = 0.9;
	mat_specular[1] = 0.9;
	mat_specular[2] = 0.9;
	mat_specular[3] = 1.0;

	mat_shininess[0] = 50.0;

	/*  Spot sur light1 */
	light1_specular[0] = 1.0;
	light1_specular[1] = 1.0;
	light1_specular[2] = 1.0;
	light1_specular[3] = 1.0;

	light1_ambient[0] = 0.0f;
	light1_ambient[1] = 0.0f;
	light1_ambient[2] = 0.0f;
	light1_ambient[3] = 1.0f;

	light1_diffuse[0] = 1.0f; // donner une couleur au spot
	light1_diffuse[1] = 1.0f;
	light1_diffuse[2] = 0.6f;
	light1_diffuse[3] = 1.0f;

	light1_position[0] = 0.0; //position de la source de lumière, positionnelle
	light1_position[1] = 0.0;
	light1_position[2] = 4.0;
	light1_position[3] = 1.0;

	// direction du spot
	light1_direction[0] = 0.0;
	light1_direction[1] = 0.0;
	light1_direction[2] = -1.0;

	/* Lumière pour le vue en perspective sur light2*/
	light2_position[0] = 0.0f;
	light2_position[1] = 0.0f;
	light2_position[2] = 1.0f;
	light2_position[3] = 0.0f; //position de la source de lumière, directionelle

	light2_ambient[0] = 0.2f;
	light2_ambient[1] = 0.2f;
	light2_ambient[2] = 0.2f;
	light2_ambient[3] = 1.0f;

	light2_diffuse[0] = 0.7f;
	light2_diffuse[1] = 0.7f;
	light2_diffuse[2] = 0.7f;
	light2_diffuse[3] = 1.0f;

	light2_specular[0] = 1.0;
	light2_specular[1] = 1.0;
	light2_specular[2] = 1.0;
	light2_specular[3] = 1.0;


	/* Initialisation de la display list */
	initDisplayList();

	//display list particulière pour la 3D
	dlPacman3D_body = glGenLists(2);
	dlPacman3D_arm = dlPacman3D_body + 1;

	//display list vies pacman
	int base = 1.0;
	glNewList(dlPacman3D_body, GL_COMPILE);
	dessinePacman3DBody(base);
	glEndList();

	//display list vies pacman
	glNewList(dlPacman3D_arm, GL_COMPILE);
	dessinePacman3DArm(base);
	glEndList();
}

void View3D::applySmoothLookAt(){
	PacMan pacman = model->getPacMan();
	if (pacman.getCurrentDirection() == RIGHTDIRECTION) {
		pacmanX = pacmanX  + smoothlag;
	} else if (pacman.getCurrentDirection() == LEFTDIRECTION
			|| pacman.getCurrentDirection() == NONEDIRECTION) {
		pacmanX = pacmanX  - smoothlag;
	} else if (pacman.getCurrentDirection() == UPDIRECTION) {
		pacmanY = pacmanY  + smoothlag;
	} else {
		pacmanY = pacmanY  - smoothlag;
	}
}

/* Méthode permettant de dessiner le pacman */
void View3D::dessinePacman() {
	PacMan pacman = model->getPacMan();
	glPushMatrix();
	glRotatef(pacman.getSpin(), 0.0, 0.0, 1.0); // Applique une rotation selon l'angle récupéré dans le modèle
	glScalef(0.5, 0.5, 0.5);
	glRotatef(-90.0, 0.0, 0.0, 1.0);
	glRotatef(90.0, 1.0, 0.0, 0.0);
	

	if (pacman.isModeSuper()) {
		glColor3f(1.0, 0.0, 0.0); // Définit la couleur du carré
	} else { //mode normal
		glColor3f(1.0, 0.8, 0.0);
	}
	glCallList(dlPacman3D_body);
	if (model->isDrawAnimation()) {
		glRotated(tempAngleDebut, 1, 0, 0); // Faire bouger le bars
	}
	glCallList(dlPacman3D_arm);

	glPopMatrix();
}

/*
 * Méthode permettant de dessiner un ghost(voiture) avec la couleur du gyrophare passé en paramètre
 */
void View3D::dessineGhost(float r, float g, float b) {
	glPushMatrix(); 

	glRotatef(90.0, 1.0, 0.0, 0.0);
	glRotatef(90.0, 0.0, 1.0, 0.0);
	glTranslatef(-0.2, 0.0, -0.2);
	glScalef(0.2, 0.2, 0.2);

	glBegin(GL_QUADS);
	glColor3f(1.0, 1.0, 1.0);
	glNormal3f(0.0,1.0,0.0);
	/*toit*/
	glVertex3f(2.0, 1.0, 0.0);
	glVertex3f(0.0, 1.0, 0.0);
	glVertex3f(0.0, 1.0, 2.0);
	glVertex3f(2.0, 1.0, 2.0);
	/*vitre1*/
	glColor3f(0.7, 0.7, 1.0);
	glVertex3f(2.0, 0.0, -1.0);
	glVertex3f(0.0, 0.0, -1.0);
	glVertex3f(0.0, 1.0, 0.0);
	glVertex3f(2.0, 1.0, 0.0);
	/*vitre2*/
	glVertex3f(2.0, 1.0, 2.0);
	glVertex3f(0.0, 1.0, 2.0);
	glVertex3f(0.0, 0.0, 3.0);
	glVertex3f(2.0, 0.0, 3.0);
	
	glColor3f(1.0, 1.0, 1.0);
	/*capot*/
	glVertex3f(2.0, 0.0, -2.0);
	glVertex3f(0.0, 0.0, -2.0);
	glVertex3f(0.0, 0.0, 4.0);
	glVertex3f(2.0, 0.0, 4.0);

	glColor3f(1.0, 1.0, 1.0);
	/*côté gauche*/
	glNormal3f(0.0,1.0,1.0);
	glVertex3f(0.0, 0.0, -1.0);
	glVertex3f(0.0, 0.0, 3.0);
	glVertex3f(0.0, 1.0, 2.0);
	glVertex3f(0.0, 1.0, 0.0);

	/*côté droit*/
	glVertex3f(2.0, 0.0, -1.0);
	glVertex3f(2.0, 0.0, 3.0);
	glVertex3f(2.0, 1.0, 2.0);
	glVertex3f(2.0, 1.0, 0.0);

	//Partie du bas
	/*pare-choc avant*/
	glVertex3f(2.0, 0.0, -2.0);
	glVertex3f(0.0, 0.0, -2.0);
	glVertex3f(0.0, -1.0, -2.0);
	glVertex3f(2.0, -1.0, -2.0);
	/*pare-choc arrière*/
	glVertex3f(2.0, 0.0, 4.0);
	glVertex3f(0.0, 0.0, 4.0);
	glVertex3f(0.0, -1.0, 4.0);
	glVertex3f(2.0, -1.0, 4.0);
	/*côté gauche bas*/
	glVertex3f(0.0, -1.0, -2.0);
	glVertex3f(0.0, -1.0, 4.0);
	glVertex3f(0.0, 0.0, 4.0);
	glVertex3f(0.0, 0.0, -2.0);
	/*côté droit bas*/
	glVertex3f(2.0, -1.0, -2.0);
	glVertex3f(2.0, -1.0, 4.0);
	glVertex3f(2.0, 0.0, 4.0);
	glVertex3f(2.0, 0.0, -2.0);
	/*dessous de la voiture*/
	glVertex3f(0.0, -1.0, -2.0);
	glVertex3f(0.0, -1.0, 4.0);
	glVertex3f(2.0, -1.0, 4.0);
	glVertex3f(2.0, -1.0, -2.0);

	glEnd();

	/*roues*/
	glPushMatrix();
	glColor3f(0.2, 0.2, 0.2);
	glTranslatef(0.0, -1.3, 3.0);
	glRotatef(90.0, 0.0, 1.0, 0.0);
	gluCylinder(quadBiere, 0.5, 0.5, 0.5, 20, 20);
	gluDisk(quadBiere, 0.5, 0.0, 20, 20);
	glPopMatrix();

	glPushMatrix();
	glColor3f(0.2, 0.2, 0.2);
	glTranslatef(1.5, -1.3, 3.0);
	glRotatef(90.0, 0.0, 1.0, 0.0);
	gluCylinder(quadBiere, 0.5, 0.5, 0.5, 20, 20);
	gluDisk(quadBiere, 0.5, 0.0, 20, 20);
	glPopMatrix();

	glPushMatrix();
	glColor3f(0.2, 0.2, 0.2);
	glTranslatef(0.0, -1.3, -1.0);
	glRotatef(90.0, 0.0, 1.0, 0.0);
	gluCylinder(quadBiere, 0.5, 0.5, 0.5, 20, 20);
	gluDisk(quadBiere, 0.5, 0.0, 20, 20);
	glPopMatrix();

	glPushMatrix();
	glColor3f(0.2, 0.2, 0.2);
	glTranslatef(1.5, -1.3, -1.0);
	glRotatef(90.0, 0.0, 1.0, 0.0);
	gluCylinder(quadBiere, 0.5, 0.5, 0.5, 20, 20);
	gluDisk(quadBiere, 0.5, 0.0, 20, 20);
	glPopMatrix();

	/*gyrophare*/
	glPushMatrix();

	glColor3f(r, g, b);
	glTranslatef(1.0, 1.0, 1.0);
	glutSolidSphere(0.5, 10, 10);

	glPopMatrix();

	/*ligne sur le côté*/
	 glColor3f(1.0, 0.6, 0.0);
	 glBegin(GL_QUADS);
	/*côté gauche bas*/
	glVertex3f(-0.1, -0.5, -2.0);
	glVertex3f(-0.1, -0.5, 4.0);
	glVertex3f(-0.1, -0.1, 4.0);
	glVertex3f(-0.1, -0.1, -2.0);
	/*côté droit bas*/
	glVertex3f(2.1, -0.5, -2.0);
	glVertex3f(2.1, -0.5, 4.0);
	glVertex3f(2.1, -0.1, 4.0);
	glVertex3f(2.1, -0.1, -2.0);
	glEnd();

	glPopMatrix();
}
/*
 * Méthode permettant de dessiner une bière
 */
void View3D::dessineBiere() {
	int parts = 20; //slides & stacks
	float cylBaseRadB = 0.1;
	float cylTopRadB = 0.2;
	float lenghtBiere = 0.8;
	float lenghtMousse = 0.1;

	glPushMatrix();

	glTranslated(0, 0, -CELL_SIZE / 2); //collé au sol
	glColor3f(1.0, 1.0, 0.23); //jaune
	gluDisk(quadBiere, 0, cylBaseRadB, parts, parts); //disque en dessous du cylindre

	glPushMatrix();
	glTranslated(0, 0, lenghtBiere + lenghtMousse); //déplacement du disque au dessus de la mousse
	glColor3f(1, 1, 1); //blanc
	gluDisk(quadBiere, 0, cylTopRadB, parts, parts); //disque dessus
	glPopMatrix();

	glColor4f(1.0, 1.0, 0.23, 0.4); //jaune légérement transparent
	gluCylinder(quadBiere, cylBaseRadB, cylTopRadB, lenghtBiere, parts, parts); //cylindre biere

	glPushMatrix();
	glTranslated(0, 0, lenghtBiere); //positionne la mousse au dessus de la bière
	glColor3f(1, 1, 1); //blanc
	gluCylinder(quadBiere, cylTopRadB, cylTopRadB, lenghtMousse, parts, parts); //cylindre mousse
	glPopMatrix();

	glPopMatrix();
}

/*
 * Méthode permettant de dessiner une bouteille de Whiskey (Super Pastille)
 */
void View3D::dessineJackie() {
	int parts = 20;

	glPushMatrix();
	glTranslatef(0.0, 0.0, -CELL_SIZE / 2);

	
	glColor3f(0.96, 0.47, 0.0);
	gluCylinder(quadBiere, 0.2, 0.2, 0.2, parts, parts); //cylindre verre


	glPushMatrix();
	glTranslatef(0.0, 0.0, 0.2);
	glColor3f(0.0, 0.0, 0.0);
	gluCylinder(quadBiere, 0.2, 0.2, 0.3, parts, parts); //cylindre étiquette de la bouteille
	glPopMatrix();

	glPushMatrix();
	glTranslatef(0.0, 0.0, 0.5);
	glColor3f(0.96, 0.47, 0.0);
	gluCylinder(quadBiere, 0.2, 0.2, 0.2, parts, parts); //cylindre verre
	glPopMatrix();

	glPushMatrix();
	glTranslatef(0.0, 0.0, 0.7);
	gluDisk(quadBiere, 0, 0.2, parts, parts); //disque pour fermer le cou de la bouteille
	glPopMatrix();

	glPushMatrix();
	glTranslatef(0.0, 0.0, 0.7);
	glColor3f(0.96, 0.47, 0.0);
	gluCylinder(quadBiere, 0.1, 0.1, 0.4, parts, parts); //cylindre verre
	glPopMatrix();

	glPushMatrix();
	glTranslatef(0.0, 0.0, 1.1);
	glColor3f(0.0, 0.0, 0.0);
	gluCylinder(quadBiere, 0.1, 0.1, 0.2, parts, parts); //cylindre bouchon
	glPopMatrix();

	glPushMatrix();
	glColor3f(0.0, 0.0, 0.0);
	glTranslatef(0.0, 0.0, 1.3);
	gluDisk(quadBiere, 0, 0.1, parts, parts); //disque pour fermer le bouchon
	glPopMatrix();

	glPopMatrix();
}

/*
 * dessine un mur interieur
 */
void View3D::dessineMurInterieur() {

	glColor3f(0.76, 0.76, 0.76); //gris
	glutSolidCube(CELL_SIZE); //représente un demi-mur soit càd un cube

}
/*
 * dessine un mur extérieur horizontal
 */
void View3D::dessineMurExHor() {
	int parts = 20; //slides & stacks
	float cylBaseRadB = 0.2;
	float cylTopRadB = 0.2;
	float lenghtMur = 1;
	glColor3f(0.0, 0.0, 0.0);
	glPushMatrix();
	glTranslated(-CELL_SIZE / 2, 0, 0);
	glRotatef(90, 0, 1, 0);
	gluCylinder(quadBiere, cylBaseRadB, cylTopRadB, lenghtMur, parts, parts);
	glPopMatrix();
}

/*
 * dessine un mur extérieur vertical
 */
void View3D::dessineMurExVert() {
	int parts = 20; //slides & stacks
	//var cylindre biere
	float cylBaseRadB = 0.2;
	float cylTopRadB = 0.2;
	float lenghtMur = 1;
	glColor3f(0.0, 0.0, 0.0);
	glPushMatrix();
	glTranslated(0, CELL_SIZE / 2, 0);
	glRotatef(90, 1, 0, 0);
	gluCylinder(quadBiere, cylBaseRadB, cylTopRadB, lenghtMur, parts, parts);
	glPopMatrix();
}
/*
 * dessine le coin d'un angle.
 * Pas stocké dans une display list car nous avons que les
 * coins soient dessinés en immediate mode.
 * Nous nous permettons de faire cela car cet un élément graphique qui ne
 * sera dessiné que 4 fois toutes les itérations
 */
void View3D::dessineCoinExt(int i, int j) {
	glColor3f(1.0, 1.0, 1.0);
	glutSolidCube(CELL_SIZE);
}

/*
 * dessine mur de prison
 */
void View3D::dessineMurPrison() {
	glColor4f(0.0, 1.0, 0.0, 0.9);
	glutWireCube(CELL_SIZE);
}

/*
 * dessine un point au centre du plateau permettant de faire une rotation sur le plateau
 */
void View3D::dessineMiddlePoint() {
	if (model->isSelectionMode()) {
		glLoadName(1);
	}
	glPushMatrix();
	glColor3f(1, 0, 0);
	glTranslated(0, 0, 1);
	glutSolidSphere(CELL_SIZE / 4.0, 10, 10);
	glPopMatrix();
}
/*
 * Méthode permettant d'appliquer une décalage sur
 * l'animation de la bouche du pacman de sorte qu'au final avec plusieurs décalages,
 * l'animation donne l'impression d'avoir un mouvement continu
 */
void View3D::applySmoothPacmanAnimation(float smoothLag) {
	PacMan pacman = model->getPacMan();
	float angleDebut_from;
	float angleDebut_to;
	if (pacman.getAngleBras() == ANGLEBRASDEBUT) { //il faut aller de 0 à -55
		angleDebut_from = ANGLEBRASDEBUT;
		angleDebut_to = ANGLEBRASFIN;
	} else { // de -55 à 0
		angleDebut_from = ANGLEBRASFIN;
		angleDebut_to = ANGLEBRASDEBUT;
	}
	/*
	 * incrémente pas à pas l'angle qui sera étalonné sur 'SMOOTHLAG(10)' pas afin d'arriver à l'autre état
	 *
	 */
	float smoothAngleDebut = angleDebut_to
			- ((angleDebut_to - angleDebut_from) * smoothLag);

	tempAngleDebut = smoothAngleDebut; //set nouvelle valeur pour dessin
}

/* Méthode qui dessine le corps du pacman */
void View3D::dessinePacman3DBody(float base) {

	//corps
	glPushMatrix();

	glutSolidSphere(base, 50, 50);
	glPopMatrix();
	//yeux
	glPushMatrix();
	glTranslated(0.5, 1.0, 0.0);
	glColor3f(1.0, 1.0, 1.0);
	glutSolidSphere(base / 5, 10, 10);
	glPopMatrix();
	glPushMatrix();
	glTranslated(-0.5, 1.0, 0.0);
	glColor3f(1.0, 1.0, 1.0);
	glutSolidSphere(base / 5, 10, 10);
	glPopMatrix();
	//pupilles
	glPushMatrix();
	glTranslated(-0.5, 1.0, 0.2);
	glColor3f(0.0, 0.0, 0.0);
	glutSolidSphere(base / 9, 10, 10);
	glPopMatrix();
	glPushMatrix();
	glTranslated(0.5, 1.0, 0.2);
	glColor3f(0.0, 0.0, 0.0);
	glutSolidSphere(base / 9, 10, 10);
	glPopMatrix();

}

/* Méthode qui dessine le bras du pacman */
void View3D::dessinePacman3DArm(float base) {

	//épaule
	glPushMatrix();
	glTranslated(0.9, 0.5, 0);
	glColor3f(1.0, 1.0, 1.0);
	glutSolidSphere(base / 8, 10, 10);
	glPopMatrix();
	//BRAS
	glPushMatrix();
	glTranslated(0.9, 0.5, 0);
	glRotated(45, 1, 0, 0);
	glRotated(15, 0, 1, 0);
	glColor3f(1.0, 1.0, 1.0);
	gluCylinder(quadBiere, base / 9, base / 9, 1.1, 10, 10);
	glPopMatrix();
	//coude
	glPushMatrix();
	glTranslated(1.2, -0.3, 0.8);
	glColor3f(1.0, 1.0, 1.0);
	glutSolidSphere(base / 8, 10, 10);
	glPopMatrix();
	//avant bras
	glPushMatrix();
	glTranslated(1.2, -0.3, 0.8);
	glRotated(-75, 0, 1, 0);
	glColor3f(1.0, 1.0, 1.0);
	gluCylinder(quadBiere, base / 9, base / 9, 1.1, 10, 10);
	glPopMatrix();
	//Main
	glPushMatrix();
	glTranslated(0.1, -0.3, 1.1);
	glColor3f(1.0, 1.0, 1.0);
	glutSolidSphere(base / 8, 10, 10);
	glPopMatrix();

}

/* Active les lumières et autres paramètres liés au lumières, au matériaux et 3D */
void View3D::enableLight(){

	glEnable(GL_NORMALIZE);
	glEnable(GL_COLOR_MATERIAL); //afficher les couleurs lorsque l'éclairage est activé
	glEnable(GL_DEPTH_TEST); //active le test de profondeur
	glEnable(GL_BLEND);
	glEnable(GL_LIGHTING); // on active l'éclairage
	glDepthFunc(GL_LEQUAL);

	glEnable(GL_COLOR_MATERIAL); //afficher les couleurs lorsque l'éclairage est activé

	/*On active la shininess(brilliance)*/
	glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
	glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
	glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
	glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
	/*Modele lumiere*/
	glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
}
